﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
// Qt headers
#include <QEvent>
#include <QMouseEvent>
#include <QDir>
#include <QPainter>
#include <QApplication>
#include <QLineEdit>
#include <QComboBox>
#include <QUuid>
#include <qmath.h>

// custom headers
#include "EditingParameterItemDelegate.h"
#include "EditingParametersTableSymbols.h"
#include "PaintingUtils.h"
#include "MessageDialog.h"
#include "UIService.h"
#include "GcsMainWindow.h"
#include "IParameter.gcs.h"
#include "IParameterDefinition.gcs.h"
#include "ParameterNameValidator.h"
#include "CustomBallon.h"
#include "WidgetUtils.h"
#include "GbmpNew.h"

#include "EnableCompileWarning_The_LAST_IncludeInCpp.h" 
namespace gmepgcs 
{
    EdittingParameterItemDelegate::EdittingParameterItemDelegate(QObject *parent) :
        QStyledItemDelegate(parent),
        m_gridColor(217, 221, 228, 255),
        m_gridWidth(1.0),
        m_selectedBackground(QColor(41, 121, 255, 25), Qt::BrushStyle::SolidPattern),
        m_selectedBorderColorNormal(QColor(41, 121, 255, 229)),
        m_selectedBorderColorError(QColor(255, 110, 110, 255)),
        m_selectedBorderColor(QColor(41, 121, 255, 229)), // pass m_selectedBorderColorNormal into m_selectedBorderColor will not work
        m_selectedBorderWidth(1)
    {
    }
    
    EdittingParameterItemDelegate::~EdittingParameterItemDelegate()
    {

    }

    void EdittingParameterItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
    {
        int col = index.column();
        int row = index.row();
        
        // draw cells
        QMargins cellMargin(8, 0, 8, 0);
        DBG_WARN_AND_RETURN_VOID_UNLESS(index.model(), L"index.model()为空", L"GMEP", L"2024-01-30");
        switch (col)
        {        
        case COL_PARAMETER_NAME: // 参数名        
        case COL_PARAMETER_VALUE: // 参数值
        case COL_PARAMETER_UNIT: // 单位
        {
            painter->save();

            // draw background
            QBrush &background = index.model()->data(index, Qt::BackgroundRole).value<QBrush>();
            painter->fillRect(option.rect, background);

            // draw text
            QString text = index.model()->data(index, Qt::DisplayRole).value<QString>();
            Qt::AlignmentFlag textAlignment = (Qt::AlignmentFlag)(index.model()->data(index, Qt::TextAlignmentRole).value<int>());
            QFont font = index.model()->data(index, Qt::FontRole).value<QFont>();
            QColor foreground = index.model()->data(index, Qt::ForegroundRole).value<QColor>();
            painter->setFont(font);
            painter->drawText(option.rect.marginsRemoved(cellMargin), text, QTextOption(textAlignment));

            painter->restore();
            break;
        }
        case COL_PARAMETER_INSTANCE_MARK: // 实例标记
        {
            painter->save();

            // draw background
            QBrush &background = index.model()->data(index, Qt::BackgroundRole).value<QBrush>();
            painter->fillRect(option.rect, background);

            // draw box
            QSize boxSize = index.model()->data(index, Qt::SizeHintRole).value<QSize>();
            if (boxSize == QSize())
            {
                boxSize = QSize(12, 12);
            }
            QPoint boxTopLeft((option.rect.width() - boxSize.width()) / 2, (option.rect.height() - boxSize.height()) / 2);
            QRect boxRect(option.rect.topLeft() + boxTopLeft, boxSize);
            QColor &foreground = index.model()->data(index, Qt::ForegroundRole).value<QColor>();
            bool isChecked = index.model()->data(index, Qt::DisplayRole).toBool();
            QColor borderColor = foreground;
            if (!isChecked)
            {
                borderColor = QColor(189, 199, 213, 255);
            }
            painter->setRenderHint(QPainter::RenderHint::HighQualityAntialiasing);
            PaintingUtils::DrawBorder(painter, boxRect, QMargins(1, 1, 1, 1), QMargins(2, 2, 2, 2), borderColor, foreground);

            // draw tick
            QString tickImageAddr = index.model()->data(index, Qt::DecorationRole).value<QString>();
            if (!tickImageAddr.isEmpty())
            {
                QRect tickRect = boxRect.marginsAdded(QMargins(-2, -2, -2, -2));
                QImage tickImage = QImage(tickImageAddr);
                painter->setRenderHint(QPainter::RenderHint::SmoothPixmapTransform);
                painter->drawPixmap(tickRect, QPixmap::fromImage(tickImage));
            }            

            painter->restore();
            break;
        }
        default:
        {
            QStyledItemDelegate::paint(painter, option, index);
            break;
        }
        }
        
        // draw grid
        painter->save();
        QPen gridPen(m_gridColor, m_gridWidth);
        painter->setPen(gridPen);
        if (col <= index.model()->columnCount() - 1)
        {
            painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
        }
        if (row <= index.model()->rowCount() - 1)
        {
            painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
        }
        painter->restore();

        // draw full-row-selected style
        if (option.state & QStyle::State_Selected)
        {
            painter->save();

            painter->fillRect(option.rect, m_selectedBackground);
            painter->setPen(QPen(m_selectedBorderColor, m_selectedBorderWidth));
            if (0 == col)
            {
                painter->drawLine(option.rect.bottomLeft(), option.rect.topLeft());
                painter->drawLine(option.rect.topLeft(), option.rect.topRight());
                painter->drawLine(option.rect.bottomRight(), option.rect.bottomLeft());
            }
            else if (index.model()->columnCount() - 1 == col)
            {
                painter->drawLine(option.rect.topLeft(), option.rect.topRight());
                painter->drawLine(option.rect.topRight(), option.rect.bottomRight());
                painter->drawLine(option.rect.bottomRight(), option.rect.bottomLeft());
            }
            else
            {
                painter->drawLine(option.rect.topLeft(), option.rect.topRight());
                painter->drawLine(option.rect.bottomRight(), option.rect.bottomLeft());
            }

            painter->restore();
        }
    }

    bool EdittingParameterItemDelegate::editorEvent(QEvent * event, QAbstractItemModel * model, const QStyleOptionViewItem & option, const QModelIndex & index)
    {
        QWidget *widget = const_cast<QWidget *>(option.widget);

        QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent*>(event);

        int32_t row = index.row();
        int32_t col = index.column();        

        IParameter *parameter = static_cast<IParameter*>(index.internalPointer());
        if (!parameter)
        {
            return QStyledItemDelegate::editorEvent(event, model, option, index);
        }
        
        if (col == COL_PARAMETER_INSTANCE_MARK)
        {
            if (parameter->IsInstanceEditable() && mouseEvent && mouseEvent->type() == QEvent::MouseButtonPress)
            {
                DBG_WARN_AND_RETURN_FALSE_UNLESS(index.model(), L"index.model()为空", L"GMEP", L"2024-01-30");
                QSize boxSize = index.model()->data(index, Qt::SizeHintRole).value<QSize>();
                if (boxSize == QSize())
                {
                    boxSize = QSize(12, 12);
                }
                QPoint boxTopLeft((option.rect.width() - boxSize.width()) / 2, (option.rect.height() - boxSize.height()) / 2);
                QRect boxRect(option.rect.topLeft() + boxTopLeft, boxSize);

                if (option.rect.contains(mouseEvent->pos()))
                {
                    bool isInstance = model->data(index, Qt::EditRole).toBool();
                    model->setData(index, !isInstance, Qt::EditRole);
                }
            }
            return false;
        }

        return QStyledItemDelegate::editorEvent(event, model, option, index);
    }
    
    QWidget * EdittingParameterItemDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const
    {
        IParameter *parameter = static_cast<IParameter*>(index.internalPointer());
        if (!parameter)
        {
            return nullptr;
        }

        int32_t row = index.row();
        int32_t col = index.column();
        

        switch (col)
        {
        case COL_PARAMETER_NAME:
        {
            QLineEdit *lineEdit = NEW_AS_QT(QLineEdit,parent);
            CustomBallon::New(lineEdit, CustomBallon::BallonIcon::Warning, std::wstring(), [=](CustomBallon *ballon) {
                if (ballon == nullptr)
                {
                    return;
                }

                connect(lineEdit, &QLineEdit::textChanged, [=](const QString & text) {
                    QString message;
                    if (ParameterNameValidator().Validate(text, message) != ParameterNameValidator::State::Acceptable)
                    {
                        ballon->ConfigMessage(message.toStdWString());
                        ballon->show();
                        m_selectedBorderColor = m_selectedBorderColorError;
                        DBG_WARN_AND_RETURN_VOID_UNLESS(index.model(), L"index.model()为空", L"GMEP", L"2024-01-30");
                        emit const_cast<QAbstractItemModel*>(index.model())->layoutChanged();
                    }
                    else
                    {
                        ballon->hide();
                        ballon->ConfigMessage(std::wstring());
                        m_selectedBorderColor = m_selectedBorderColorNormal;
                        DBG_WARN_AND_RETURN_VOID_UNLESS(index.model(), L"index.model()为空", L"GMEP", L"2024-01-30");
                        emit const_cast<QAbstractItemModel*>(index.model())->layoutChanged();
                    }
                });

                lineEdit->setProperty("errorBallonKey", ballon->Key());
            });

            return lineEdit;
        }
        case COL_PARAMETER_VALUE:
        {
            if (parameter->Definition == nullptr)
            {
                return nullptr;
            }
            if (parameter->Definition->GetValueType() == L"Logic" || parameter->Definition->GetValueType() == L"Enum")
            {
                QComboBox *pComboBox = NEW_AS_QT(QComboBox,parent);
                return pComboBox;
            }
            else
            {
                QLineEdit *lineEdit = NEW_AS_QT(QLineEdit,parent);

                if (parameter->Definition->GetValueType() == L"Int")
                {
                    // 只能输入整数
                    //lineEdit->setValidator(NEW_AS_QT(QRegExpValidator(QRegExp("^,0|[1-9][0-9]*|-[1-9][0-9]*)$")));
                    auto intValidator = NEW_AS_QT(QIntValidator);
                    lineEdit->setValidator(intValidator);
                }
                else if (parameter->Definition->GetValueType() == L"Number")
                {
                    // 只能输入浮点数
                    auto doubleValidator = NEW_AS_QT(QDoubleValidator);
                    doubleValidator->setNotation(QDoubleValidator::Notation::StandardNotation);
                    lineEdit->setValidator(doubleValidator);
                }

                return lineEdit;
            }
            break;
        }
        case COL_PARAMETER_UNIT:
        {
            QLineEdit *lineEdit = NEW_AS_QT(QLineEdit,parent);
            return lineEdit;
        }
        case COL_PARAMETER_INSTANCE_MARK:
        {
            return nullptr;
        }
        default:
        {
            break;
        }
        }

        return nullptr;
    }

    void EdittingParameterItemDelegate::setEditorData(QWidget * editor, const QModelIndex & index) const
    {
        IParameter *parameter = static_cast<IParameter*>(index.internalPointer());
        if (!parameter)
        {
            return;
        }

        int32_t col = index.column();
        switch (col)
        {
        case COL_PARAMETER_NAME:
        {
            if (QLineEdit *pLineEdit = static_cast<QLineEdit*>(editor))
            {
                DBG_WARN_AND_RETURN_VOID_UNLESS(index.model(), L"index.model()为空", L"GMEP", L"2024-01-30");
                QString data = index.model()->data(index, Qt::EditRole).value<QString>();
                pLineEdit->setText(data);
            }
            break;
        }
        case COL_PARAMETER_VALUE:
        {
            DBG_WARN_AND_RETURN_VOID_UNLESS(index.model(), L"index.model()为空", L"GMEP", L"2024-01-30");
            QString data = index.model()->data(index, Qt::EditRole).value<QString>();
            if (parameter->Definition && parameter->Definition->GetValueType() == L"Logic")
            {
                if (QComboBox *pComboBox = static_cast<QComboBox*>(editor))
                {
                    int currentIndex = -1;
                    auto logicValues = IParameter::GetLogicValues();
                    auto count = logicValues.size();
                    if (count > 0)
                    {
                        for (auto i = 0; i!= count; ++i)
                        {
                            auto key = QString::fromStdWString(logicValues[i].first);
                            auto value = QString::fromStdWString(logicValues[i].second);
                            pComboBox->addItem(value, key);

                            if (key == data)
                            {
                                currentIndex = i;
                            }
                        }
                    }
                    pComboBox->setCurrentIndex(currentIndex);
                }
            }
            else if (parameter->Definition && parameter->Definition->GetValueType() == L"Enum")
            {
                if (QComboBox *pComboBox = static_cast<QComboBox*>(editor))
                {
                    int currentIndex = -1;
                    auto enumValues = parameter->GetEnumValues();
                    auto count = enumValues.size();
                    if (count > 0)
                    {
                        for (auto i = 0; i!=count; ++i)
                        {
                            auto key =  QString::fromStdWString(enumValues[i].first);
                            auto value = QString::fromStdWString(enumValues[i].second);
                            pComboBox->addItem(value, key);

                            if (key == data)
                            {
                                currentIndex = i;
                            }
                        }
                    }
                    pComboBox->setCurrentIndex(currentIndex);
                }
            }
            else
            {
                if (parameter->Definition && parameter->Definition->GetParameterDisplayType() == L"Angle")
                {
                    auto strRadvalue = data.trimmed();
                    if (!strRadvalue.isEmpty())
                    {
                        auto ok = false;
                        auto doubleRadValue = strRadvalue.toDouble(&ok);
                        if (ok)
                        {
                            auto doubleDegValue = doubleRadValue * 180.0 / M_PI;
                            QRegExp redundantZeros;
                            redundantZeros.setPattern("(\\.){0,1}0+$");
                            auto strDegValue = QString::fromStdWString(L"%1").arg(doubleDegValue, 0, 'f', 2).replace(redundantZeros, "");
                            data = strDegValue;
                        }
                    }
                }

                if (QLineEdit *pLineEdit = static_cast<QLineEdit*>(editor))
                {
                    pLineEdit->setText(data);
                }
            }
            break;
        }
        case COL_PARAMETER_UNIT:
        {
            if (QLineEdit *pLineEdit = static_cast<QLineEdit*>(editor))
            {
                DBG_WARN_AND_RETURN_VOID_UNLESS(index.model(), L"index.model()为空", L"GMEP", L"2024-01-30");
                QString data = index.model()->data(index, Qt::EditRole).value<QString>();
                pLineEdit->setText(data);
            }
            break;
        }
        case COL_PARAMETER_INSTANCE_MARK:
        {
            break;
        }
        default:
        {
            break;
        }
        }          
    }

    void EdittingParameterItemDelegate::setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const
    {
        IParameter *parameter = static_cast<IParameter*>(index.internalPointer());
        if (!parameter)
        {
            return;
        }

        int32_t col = index.column();


        switch (col)
        {
        case COL_PARAMETER_NAME:
        {
            if (QLineEdit *pLineEdit = static_cast<QLineEdit*>(editor))
            {
                QString value = pLineEdit->text().trimmed();
                QString message;
                if (ParameterNameValidator().Validate(value, message) == ParameterNameValidator::State::Acceptable)
                {
                    model->setData(index, value, Qt::EditRole);
                }
                
                CustomBallon::Destroy(pLineEdit->property("errorBallonKey").toString());
                m_selectedBorderColor = m_selectedBorderColorNormal;
                DBG_WARN_AND_RETURN_VOID_UNLESS(index.model(), L"index.model()为空", L"GMEP", L"2024-01-30");
                emit const_cast<QAbstractItemModel*>(index.model())->layoutChanged();
            }
            break;
        }
        case COL_PARAMETER_VALUE:
        {
            if (parameter->Definition && parameter->Definition->GetValueType() == L"Logic")
            {
                if (QComboBox *pComboBox = static_cast<QComboBox*>(editor))
                {
                    auto currentIndex = pComboBox->currentIndex();
                    auto currentKey = currentIndex >= 0
                        ? pComboBox->itemData(currentIndex, Qt::ItemDataRole::UserRole)
                        : QString();
                    model->setData(index, currentKey, Qt::EditRole);
                }
            }
            else if (parameter->Definition && parameter->Definition->GetValueType() == L"Enum")
            {
                if (QComboBox *pComboBox = static_cast<QComboBox*>(editor))
                {
                    auto currentIndex = pComboBox->currentIndex();
                    auto currentKey = currentIndex >= 0
                        ? pComboBox->itemData(currentIndex, Qt::ItemDataRole::UserRole)
                        : QString();
                    model->setData(index, currentKey, Qt::EditRole);
                }
            }
            else
            {
                if (QLineEdit *pLineEdit = static_cast<QLineEdit*>(editor))
                {
                    bool flag = true;
                    QString value = pLineEdit->text().trimmed();

                    if (parameter->Definition && parameter->Definition->GetValueType() == L"Int")
                    {
                        // 对value重新赋值，以避免原值存在小数的情形
                        value = QString::number(value.toInt(&flag));
                    }
                    else if (parameter->Definition && parameter->Definition->GetValueType() == L"Number")
                    {
                        // 避免精度损失，不对value重新赋值
                        value.toDouble(&flag);
                    }
                    
                    if (parameter->Definition && parameter->Definition->GetParameterDisplayType() == L"Angle")
                    {
                        auto strDegvalue = value;
                        if (!strDegvalue.isEmpty())
                        {
                            auto doubleDegalue = strDegvalue.toDouble(&flag);
                            if (flag)
                            {
                                auto doubleRadValue = doubleDegalue * M_PI / 180.0;
                                QRegExp redundantZeros;
                                redundantZeros.setPattern("(\\.){0,1}0+$");
                                auto strRadValue = QString::fromStdWString(L"%1").arg(doubleRadValue, 0, 'f', -1).replace(redundantZeros, "");
                                value = strRadValue;
                            }
                        }
                    }

                    if (flag)
                    {
                        model->setData(index, value, Qt::EditRole);
                    }
                }
            }
            break;
        }
        case COL_PARAMETER_UNIT:
        {
            if (QLineEdit *pLineEdit = static_cast<QLineEdit*>(editor))
            {
                QString value = pLineEdit->text().trimmed();
                model->setData(index, value, Qt::EditRole);
            }
            break;
        }
        case COL_PARAMETER_INSTANCE_MARK:
        {
            break;
        }
        default:
        {
            break;
        }
        }        
    }

    void EdittingParameterItemDelegate::updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const
    {
        if (editor)
        {
            editor->setGeometry(option.rect.marginsRemoved(QMargins(1, 1, 1, 1)));
        }
    }
}
